<html>
    <head>
        <title>2048</title>
        <style type="text/css">
            #numbers {
                width: 400px;
            }
            .number {
                width: 90px;
                height: 90px;
                margin: 5px;
                background-color: gray;
                color: white;
                float: left;
                font-size: 20px;
                text-align: center;
                line-height: 90px;
            }
            #game-description {
                clear: both;
                padding: 40px 0 0 10px;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <div id="numbers">
                <div class="number" v-for="number in numbers">
                    {{ number.value }}
                </div>
            </div>
        </div>
        <p id="game-description">
            Use arrow keys to control the game.
        </p>
        <script src="node_modules/vue/dist/vue.js"></script>
        <script>
        var app = new Vue({
            el: '#app',
            data: {
                number_size: 4, // 边长
                numbers: [ // 数字列表
                ]
            },
            methods: {
                /**
                 * 走一步（按上下左右方向键）
                 * @param  {string} direction 方向
                 * @return {void}
                 */
                run: function (direction) {
                    var found = false; // 是否找到可移动数字的标志位

                    for (var i = 0; i < this.number_size; i++) { // 遍历第一层行或列
                        // 如果是空行或空列，直接跳过。
                        if (this.checkLineEmpty(i, direction)) {
                            continue;
                        }

                        // 取出该行或列的所有值，并将其移动到最前面。
                        var values = [];
                        for (var j = 0; j < this.number_size; j++) {
                            if (this.numbers[this.calcNumberSerial(i, j, direction)].value != '') {
                                values.push(this.numbers[this.calcNumberSerial(i, j, direction)].value);
                            }
                        }
                        for (var j = 0; j < this.number_size; j++) {
                            if (j < values.length) {
                                this.numbers[this.calcNumberSerial(i, j, direction)].value = values[j];
                            } else {
                                if (this.numbers[this.calcNumberSerial(i, j, direction)].value != '') {
                                    found = true;
                                }
                                this.numbers[this.calcNumberSerial(i, j, direction)].value = '';
                            }
                        }

                        // 如果有两个连续数字相同，将前者乘以2，后者之后的数字依次取代为下一个的数字。
                        var has_add = false;
                        for (var j = 0; j < this.number_size - 1; j++) {
                            var currentIndex = this.calcNumberSerial(i, j, direction);
                            var nextIndex = this.calcNumberSerial(i, j + 1, direction);
                            if (this.numbers[currentIndex].value != '' && this.numbers[currentIndex].value == this.numbers[nextIndex].value && !has_add) {
                                found = true;
                                has_add = true;
                                this.numbers[currentIndex].value *= 2;
                                for (var k = j + 1; k < this.number_size - 1; k++) {
                                    this.numbers[this.calcNumberSerial(i, k, direction)].value = this.numbers[this.calcNumberSerial(i, k + 1, direction)].value;
                                }
                                this.numbers[this.calcNumberSerial(i, this.number_size - 1, direction)].value = '';
                            }
                        }
                    }

                    // 如果找到可移动数字，随机生成一个新的数字。
                    if (found) {
                        this.newRandomNumber();
                    }

                    // 判断是否游戏结束
                    if (this.checkIsDead()) {
                        alert('You are dead! Your final score is ' + this.getTotalScore() + '.');
                    }
                },

                /**
                 * 初始化游戏
                 * @return {void}
                 */
                init: function () {
                    this.number_size_squart = Math.pow(this.number_size, 2);
                    for (var i = 0; i < this.number_size_squart; i++) {
                        this.numbers.push({value: ''});
                    }
                    for (var i = 0; i < 2; i++) {
                        this.newRandomNumber();
                    }
                },

                /**
                 * 判断是否游戏结束
                 * @return {bool}
                 */
                checkIsDead: function () {
                    if (!this.checkNumberFull()) {
                        return false;
                    }

                    // 游戏结束的标志是找不到任何两个相邻的相同数字
                    for (var i = 0; i < this.number_size; i++) {
                        for (var j = 0; j < this.number_size - 1; j++) {
                            if (
                                this.numbers[this.calcNumberSerial(i, j, "left")].value == this.numbers[this.calcNumberSerial(i, j + 1, "left")].value ||
                                this.numbers[this.calcNumberSerial(i, j, "up")].value == this.numbers[this.calcNumberSerial(i, j + 1, "up")].value
                            ) {
                                return false;
                            }
                        }
                    }

                    return true;
                },

                /**
                 * 得到当前总分
                 * @return {int}
                 */
                getTotalScore: function () {
                    var total = 0;
                    for (var i = 0; i < this.number_size_squart; i++) {
                        if (this.numbers[i].value != '') {
                            total += this.numbers[i].value;
                        }
                    }

                    return total;
                },

                /**
                 * 生成一个新的随机数字
                 * @return {void}
                 */
                newRandomNumber: function () {
                    if (this.checkNumberFull()) {
                        return;
                    }
                    while (true) {
                        var rand = Math.floor(Math.random() * this.number_size_squart);
                        if (this.numbers[rand].value == '') {
                            this.numbers[rand].value = 2;
                            break;
                        }
                    }
                },

                /**
                 * 判断数字是否已经满了
                 * @return {bool}
                 */
                checkNumberFull: function () {
                    var full = true;
                    for (var i = 0; i < this.number_size_squart; i++) {
                        if (this.numbers[i].value == '') {
                            full = false;
                        }
                    }
                    return full;
                },

                /**
                 * 根据查找序列得到数字的数组索引
                 * @param  {int} a            第一层查找序列
                 * @param  {int} b            第二层查找序列
                 * @param  {string} direction 方向
                 * @return {int}              数组索引
                 */
                calcNumberSerial: function(a, b, direction) {
                    if (direction == 'up') {
                        return this.calcNumberIndex(a, b);
                    }
                    if (direction == 'down') {
                        return this.calcNumberIndex(a, this.number_size - b - 1);
                    }
                    if (direction == 'left') {
                        return this.calcNumberIndex(b, a);
                    }
                    if (direction == 'right') {
                        return this.calcNumberIndex(this.number_size - b - 1, a);
                    }
                },

                /**
                 * 根据xy坐标得到数字的数组索引
                 * @param  {int} x            x坐标
                 * @param  {int} y            y坐标
                 * @return {int}              数组索引
                 */
                calcNumberIndex: function (x, y) {
                    return this.number_size * y + x;
                },

                /**
                 * 判断该行是否所有数字都为空
                 * @param  {int} y            y坐标
                 * @return {bool}
                 */
                checkRowEmpty: function (y) {
                    for (var i = 0; i < this.number_size; i++) {
                        if (this.numbers[this.calcNumberIndex(i, y)].value != '') {
                            return false;
                        }
                    }
                    return true;
                },

                /**
                 * 判断该列是否所有数字都为空
                 * @param  {int} x            x坐标
                 * @return {bool}
                 */
                checkColEmpty: function (x) {
                    for (var i = 0; i < this.number_size; i++) {
                        if (this.numbers[this.calcNumberIndex(x, i)].value != '') {
                            return false;
                        }
                    }
                    return true;
                },

                /**
                 * 判断该行或列是否所有数字都为空
                 * @param  {int} line         x或y坐标
                 * @param  {string} direction 方向
                 * @return {bool}
                 */
                checkLineEmpty: function (line, direction) {
                    if (direction == 'up' || direction == 'down') {
                        return this.checkColEmpty(line);
                    }
                    if (direction == 'left' || direction == 'right') {
                        return this.checkRowEmpty(line);
                    }
                }
            }
        });

        // 初始化游戏
        app.init();

        /**
         * 键盘捕捉
         */
        document.onkeydown = function(event) {
            var e = event || window.event || arguments.callee.caller.arguments[0];
            if(e && e.keyCode == 37) {
                app.run('left');
            }
            if(e && e.keyCode == 38) {
                app.run('up');
            }
            if(e && e.keyCode == 39) {
                app.run('right');
            }
            if(e && e.keyCode == 40) {
                app.run('down');
            }
        };
        </script>
    </body>
</html>
